Sending emails is a very important functionality for any website nowdays, either to send account verification emails, newsletter emails or even notification emails.
Today I’ll be showing how you can create your own EmailSender Service in ASP.NET Core using FluentEmail and Razor templates for rich HTML emails.
EmailSender interface
If you’re working with a project that implements a Clean Architecture, you’ll want to create an interface in your Core project to define what you need the service to do, as well as to have the possibility to use it from the Core project too.
public interface IEmailSender
{
Task<bool> SendUsingTemplate(string to, string subject, EmailTemplate template, object model);
}
The method SendUsingTemplate
has the following signature:
Task<bool>
, the method is async and will return true if the email was successfully sent or false otherwise.string to
, the recipient email address.string subject
, the email’s subject/title.EmailTemplate template
, which template to use (enum).object model
, since most of the time you’ll want to send more information in the email, you can pass a model (or an anonymous object) to the template.
Note: the method doesn’t require a from
email address, that’s because we’ll configure it in our Startup
class since we usually only use one.
public enum EmailTemplate
{
EmailConfirmation,
ChangeEmail
}
I use an enum for the template names to remove the chance of someone getting the template’s name wrong. Magic strings are always a bad idea.
EmailSender Implementation
Prerequisites
For the implementation of the service, you’ll only need the FluentEmail.Core
NuGet package: Install-Package FluentEmail.Core
Implementation
public class EmailSender : IEmailSender
{
private const string TemplatePath = "Web.Api.Infrastructure.Services.Emails.Templates.{0}.cshtml";
private readonly IFluentEmail _email;
private readonly ILogger<EmailSender> _logger;
public EmailSender(IFluentEmail email, ILogger<EmailSender> logger)
{
_email = email;
_logger = logger;
}
public async Task<bool> SendUsingTemplate(string to, string subject, EmailTemplate template, object model)
{
var result = await _email.To(to)
.Subject(subject)
.UsingTemplateFromEmbedded(string.Format(TemplatePath, template), ToExpando(model), GetType().Assembly)
.SendAsync();
if (!result.Successful)
{
_logger.LogError("Failed to send an email.\n{Errors}", string.Join(Environment.NewLine, result.ErrorMessages));
}
return result.Successful;
}
}
The code is pretty straight-forward, we use FluentEmail’s IFluentEmail
object (that gets injected) to send the email asynchronously.
I often prefer to log the errors in the service and only return a boolean response. I don’t think the client needs to know why the email wasn’t sent, the developpers need to deal with that.
The only thing you’ll need to change is the TemplatePath
constant, which needs to contain the full namespace name where your template files (.cshtml files) reside.
In order to be able to use anonymous objects in the templates, we’ll need to convert them into Expando objects. This is a “limitation” that RazorLight (the library that FluentEmail uses to handle Razor Templates) has.
private static ExpandoObject ToExpando(object model)
{
if (model is ExpandoObject exp)
{
return exp;
}
IDictionary<string, object> expando = new ExpandoObject();
foreach (var propertyDescriptor in model.GetType().GetTypeInfo().GetProperties())
{
var obj = propertyDescriptor.GetValue(model);
if (obj != null && IsAnonymousType(obj.GetType()))
{
obj = ToExpando(obj);
}
expando.Add(propertyDescriptor.Name, obj);
}
return (ExpandoObject)expando;
}
private static bool IsAnonymousType(Type type)
{
bool hasCompilerGeneratedAttribute = type.GetTypeInfo()
.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false)
.Any();
bool nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
bool isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;
return isAnonymousType;
}
The code might look alien but all it does is convert every anonymous object to an Expando object recursively (since anonymous objects can have anonymous objects…).
Razor Templates
Based on your TemplatePath
value, you will need to put all your cshtml template files in that folder and they must be marked as: Embedded resource.
For example, this my ChangeEmail template file:
Configuration
Prerequisites
When configuring the EmailSender service, you’ll need the following NuGet packages:
- FluentEmail.Smtp
- FluentEmail.Razor
Services
// Configure IFluentEmail
services.AddFluentEmail(Configuration["email_address"])
.AddRazorRenderer()
.AddSmtpSender(new SmtpClient("smtp.gmail.com", 587)
{
Credentials = new NetworkCredential(Configuration["email_address"], Configuration["email_password"]),
EnableSsl = true
});
// Add our service
services.TryAddScoped<IEmailSender, EmailSender>();
In this example I’m using a GMAIL account to send the emails, you can use whatever email server you want, even your own.
Conclusion
As you can see, creating an EmailSender service is pretty easy and straight-forward and can be used in all your projects. Having the ability to send rich HTML emails is a must for every website nowdays.